﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Affinity.Synthesis
{
    /// <summary>
    /// Takes an expression tree, isolates the top level node, and creates a LambdaExpression whose body consists of just that 
    /// top level node. The sub-nodes feeding the original top level node are replaced with ParameterExpressions to represent
    /// input values that have not yet been satisfied.
    /// 
    /// The result of "atomization" is always a LambdaExpression taking zero or more parameters and a body whose only nested
    /// expressions are ParameterExpressions.
    /// </summary>
    internal class ExpressionAtomizer
    {
        /// <summary>
        /// Turns the given expression into an atomic lambda expression: nested expressions are replaced with parameter expressions,
        /// isolating the top-level node and exposing its inputs.
        /// </summary>
        /// <param name="expression">A non-null expression.</param>
        /// <returns>A LambdaExpression whose body takes only ParameterExpressions as inputs.</returns>
        public LambdaExpression Atomize(Expression expression)
        {
            return _Visitor.Atomize(expression);
        }

        private Visitor _Visitor = new Visitor();

        /// <summary>
        /// The visitor class that performs atomization.
        /// </summary>
        private class Visitor : ExpressionVisitor
        {
            /// <summary>
            /// Turns the given expression into an atomic lambda expression: nested expressions are replaced with parameter expressions,
            /// isolating the top-level node and exposing its inputs.
            /// </summary>
            /// <param name="expression">A non-null expression.</param>
            /// <returns>A LambdaExpression whose body takes only ParameterExpressions as inputs.</returns>
            public LambdaExpression Atomize(Expression expression)
            {
                _Expression = expression;
                _Parameters.Clear();
                return (LambdaExpression)Visit(expression);
            }

            /// <summary>
            /// Overrides the general Visit(Expression) method.
            /// </summary>
            public override Expression Visit(Expression node)
            {
                Expression outputNode = null;

                if (node == _Expression)
                {
                    node = base.Visit(node);
                    outputNode = Expression.Lambda(node, _Parameters);
                }
                else if (node is ParameterExpression)
                {
                    outputNode = node;
                    _Parameters.Add((ParameterExpression)node);
                }
                else if (node != null)
                {
                    outputNode = Expression.Parameter(node.Type);
                    _Parameters.Add((ParameterExpression)outputNode);
                }

                return outputNode;
            }

            /// <summary>
            /// Overrides the VisitMemberInit(MemberInitExpression) method.
            /// </summary>
            protected override Expression VisitMemberInit(MemberInitExpression node)
            {
                return Expression.MemberInit(Expression.New(
                    node.NewExpression.Constructor,
                    node.NewExpression.Arguments.Select(argument => Visit(argument)).ToArray()),
                    node.Bindings.Select(binding => VisitMemberBinding(binding)).ToArray());
            }
            
            private Expression _Expression = null;
            private List<ParameterExpression> _Parameters = new List<ParameterExpression>();
        }
    }
}
